#include<stdio.h>
#include<Windows.h>
#include<malloc.h>
#include<conio.h>//包含按键读取函数
#define width 80 //地图宽度 宏定义方便后期修改
#define highth 30//地图高度 y方向占1
#include<mmsystem.h>//音乐播放库函数
#pragma comment(lib,"winmm.lib")
#define speed 200 //初始速度
//光标控制函数
void SetPos(int x, int y)//设置光标位置(就是输出显示的开始位置)
{
	/*  COORD是Windows API中定义的一种结构体
	 *	typedef struct _COORD
	 *	{
	 *		SHORT X;
	 *		SHORT Y;
	 *	} COORD;
	 *
	 */
	COORD pos;
	pos.X = x;
	pos.Y = y;
	HANDLE output = GetStdHandle(STD_OUTPUT_HANDLE);//获得 标准输出的句柄
	SetConsoleCursorPosition(output, pos); //设置控制台光标位置
}
//画地图
void DrawMap()//每个单词首字母用大写
{
	for (int i = 0; i <= width; i += 2)//两行（y坐标不变）
	{
		SetPos(i, 0);
		printf("■");
		SetPos(i, highth);
		printf("■");
	}
	for (int i = 0; i <= highth; i++)//两列（x坐标不变）
	{
		SetPos(0, i);
		printf("■");
		SetPos(width, i);
		printf("■");
	}
}
//蛇结构体
typedef	struct s_snake
{
	int x, y;//只需存储光标位置 使方格指定位置输出
	int direction;
	struct s_snake* p;
}snake;
enum direction { up, down, left, right };//定义一个方向的枚举类型
snake* end; 	//创建尾部
snake* pmove;//辅助遍历指针（记得每次使用初始化)
snake* food;//借用结构体x,y坐标
snake* add;
char key;
int grade;//记录分数
int V;//设置速度
//初始化蛇  坐标 长度 方向 速度
void InitSnake()
{
	end = (snake*)malloc(sizeof(snake));
	end->x = 10;
	end->y = 10;
	end->p = NULL;
	for (int i = 1; i <= 3; i += 1)
	{
		snake* sbody = (snake*)malloc(sizeof(snake));//存储蛇身的坐标 每次都需要重新申请空间
		sbody->x = 10 + 2 * i;
		sbody->y = 10;
		sbody->p = end;	// 使用头插法:每次先让新结点指向头指针指向（连接新结点与链表）
		end = sbody;	//记录结点 给下一个结点连接链表
	}
	end->direction = right;//一直使end为表头 后面只需在新增的表头的diretion改变就行 然后读取end->diretion用以控制移动
	grade = 0;
	V = speed;
}
//创建食物
void creatfood()
{
	food = (snake*)malloc(sizeof(snake));
	srand(time(0));//设置随机数种子
	 //判断：在地图中
	food->y = rand() % (highth -2)+1;//rand()%num产生 0-num-1(数组),rand产生范围数公式rand()%(m)+n;有效范围在 [n,m-1+n]
	food->x = rand() % (width - 4) + 2;//x:2-78 y:1-29;
	while (food->x %2)	food->x = rand() % (width - 4) + 2;	 //x为偶数坐标 
	pmove = end;//遍历是否在地图及蛇身上
	while (pmove != NULL)
	{
		if (food->x == pmove->x && food->y == pmove->y)
		{
			free(food);
			creatfood();
		}
		pmove = pmove->p;
	}
	SetPos(food->x, food->y);
	printf("■");
 }
//蛇的正式移动 删除旧蛇尾 打印蛇 
void drawNewsnake()
{
	add = (snake*)malloc(sizeof(snake));//先添加 再删除 再遍历链表画蛇
	switch (end->direction)//这里只需检测表头的方向（因为每次增头删尾）不是输入的键值
	{
	case right:
		add->x = end->x + 2;//头插法连接新头
		add->y = end->y;
		add->p = end;
		add->direction = right;
		end = add;//重新让end成为表头；(不断的错误 似乎就是缺少这一步 导致表头为add而无法遍历)
		break;
	case left:
		add->x = end->x - 2;//头插法连接新头
		add->y = end->y;
		add->p = end;
		add->direction = left;
		end = add;
		break;
	case up:
		add->x = end->x;//头插法连接新头
		add->y = end->y - 1;
		add->p = end;
		add->direction = up;
		end = add;
		break;
	case down:
		add->x = end->x;//头插法连接新头
		add->y = end->y + 1;
		add->p = end;
		add->direction = down;
		end = add;
		break;
	}
	if (end->x == food->x && end->y == food->y)
	{
		grade += 10;
		creatfood();
	}
	else
	{
		pmove = end;
		while (pmove->p->p != NULL)pmove = pmove->p;//此时pmove指向链表倒数第二个数据 尾二
		SetPos(pmove->p->x, pmove->p->y);
		printf("  ");
		free(pmove->p);
		pmove->p = NULL;
		pmove = end;
	}
	while (pmove != NULL)
	{
		SetPos(pmove->x, pmove->y);
		printf("■");
		pmove = pmove->p;
	}
	V = speed + grade / 2;//吃一个食物速度+50
	Sleep(V);
}
//用读取按键输入
void keydown()//需要返回值
{
	if (_kbhit())// kbhit()函数检查是否有键盘输入.有则往下运行
	{
		char key = _getch();//<conio.h>库
		switch (key)
		{
		case 'W':case 'w':case 72:
			if (end->direction != down)
				end->direction = up;
			break;
		case 'S':case 's':case 80:
			if (end->direction != up)
				 end->direction = down;
			break;
		case 'a':case 'A':case 75:
			if (end->direction != right)
				 end->direction = left;
			break;
		case 'd':case 'D':case 77:
			if (end->direction != left)
				 end->direction = right;
			break;
		case 32:
			SetPos(20, 32);
			system("pause");
		}
	}
}
//设置规则：若蛇撞到墙 或者咬到自己 结束 
HWND hwnd = NULL;//定义窗口句柄
int Gamerule()
{
	if (end->x == 0 || end->x == width || end->y == 0 || end->y == highth)
	{
		SetPos(10, 15);
		MessageBox(hwnd, "撞墙了，游戏结束", "gameover", MB_OK);//MessageBox(句柄, 显示内容,标题,MB_OK)
		return 0;
	}
	pmove = end->p;//自己是否咬到自己
	while (pmove != NULL)
	{
		if (end->x == pmove->x && end->y == pmove->y)
		{
			SetPos(10, 15);
			MessageBox(hwnd, "你咬到自己了，游戏结束", "gameover", MB_OK);//MessageBox(句柄, 显示内容,标题,MB_OK)
			return 0;
		}
		pmove = pmove->p;
	}
}
//右侧提示信息
void printTips()
{
	SetPos(width + 6, 7);
	printf("得分：%d", grade);
	SetPos(width + 6, 8);
	printf("当前速度：%d", 1000-V);
	SetPos(width + 6, 9);
	printf("按空格键暂停/继续");
}
//欢迎界面
void welcome()
{
	SetPos(25, 8);
	printf("【贪吃蛇】   作者：贪吃蛇老爸");
	SetPos(25, 11);
	printf("【游戏规则】");
	SetPos(25, 12);
	printf("1、不能撞墙、咬到自己");
	SetPos(25, 13);
	printf("2、按空格暂停");

	printf("\n");
	SetPos(30, 15);
	system("pause");//暂停 
	system("cls");//清屏 
}
main()
{	
	welcome();
	InitSnake();
	DrawMap();
	creatfood();
	PlaySound(TEXT("hmx.wav"), NULL, SND_FILENAME | SND_ASYNC | SND_LOOP);//音乐名字为hmx
	while (Gamerule())//用函数返回值判断是否继续游戏
	{	
		drawNewsnake();
		keydown();
		printTips();
	}
	SetPos(0, 34);
}
//先让蛇初始化移动 检查按键输入 若无输入 则继续初始化移动
//加入食物 ：在画新蛇函数中 在设置好蛇头坐标后 加个条件 若add坐标与food坐标相等 则添加food到蛇头而不删除 并使food重新生成
// GetAsyncKeyState函数功能：读取的是物理键状态，也就是不管你怎么鼠标键盘映射(具体的值)，它只读取实际的按键状态
//GetAsyncKeyState按键不按或抬起后不按的返回值0x0 
